package com.q3d.demo.common.lib.logger.aspect;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.catalina.connector.ResponseFacade;
import org.apache.skywalking.apm.toolkit.trace.ActiveSpan;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import com.alibaba.fastjson2.JSON;
import com.q3d.demo.common.lib.ip.IpUtilForHttpServletRequest;
import com.q3d.demo.common.lib.logger.runner.LogRunner;
import com.q3d.demo.common.lib.service.response.ResponseBase;

import lombok.extern.slf4j.Slf4j;

@Slf4j /** Lombok 的 Logger 注解 */
@Component /** 注入组件 */
@Aspect
/**
 * 请求统一日志切面
 * 
 * @author 叶湘 ( weixin:yexiang841, email:yexiang841@qq.com )
 */
public class LogAspect {

    @Value(value = "${spring.application.name}") /** 读取配置文件（或远程配置）数据 */
    public String applicationName;

    @Value(value = "${server.port}") /** 读取配置文件（或远程配置）数据 */
    public String serverPort;

    /**
     * 定义切点，对项目中所有的 controller进行拦截
     */
    @Pointcut(value = "execution(* com..controller..*.*(..))")
    public void pointcut() {
    }

    @SuppressWarnings(value = "unused")
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 请求开始执行时间
        long startTime = System.currentTimeMillis();
        // 只有当 LogRunner 启动才会做日志记录
        // 请求时间写入线程缓存
        LogRunner.threadLocalTime.set(Long.valueOf(startTime));
        // 将服务名+端口写入线程缓存
        LogRunner.threadLocalName.set(applicationName + "-" + serverPort);
        // 获取请求结构体
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest();
        // 获取来源代理 IP 地址
        String addr = httpServletRequest.getRemoteAddr();
        // 获取来源真实 IP 地址
        String ipv4 = IpUtilForHttpServletRequest.getRemoteIPv4(httpServletRequest);
        // 获取请求类型
        String method = httpServletRequest.getMethod();
        // 获取请求路径
        String uri = httpServletRequest.getRequestURI();
        // 获取请求类名
        String clazz = proceedingJoinPoint.getTarget().getClass().getSimpleName();
        // 获取请求方法名
        String func = proceedingJoinPoint.getSignature().getName();
        // 获取请求参数
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
        // 获取请求参数类型
        String[] parameterNames = methodSignature.getParameterNames();
        // 获取请求参数值
        Object[] parameterValues = proceedingJoinPoint.getArgs();
        // 组合请求参数，进行日志打印
        StringBuilder requestDataBuilder = new StringBuilder();
        if (parameterNames != null && parameterNames.length > 0) {
            for (int i = 0; i < parameterNames.length; i++) {
                Object parameterName = parameterNames[i];
                Object parameterValue = parameterValues[i];
                // 排除几种请求参数类型，否则转 JSON 会出错
                if (parameterValue instanceof MultipartFile) {
                    continue;
                } else if (parameterValue instanceof MultipartFile[]) {
                    continue;
                } else if (parameterValue instanceof ResponseFacade) {
                    continue;
                }
                // 转 JSON
                if ((parameterValue instanceof HttpServletRequest)
                        || (parameterValue instanceof HttpServletResponse)) {
                    requestDataBuilder.append(parameterName).append("=").append(parameterValue);
                } else {
                    requestDataBuilder.append(parameterName).append("=")
                            .append(JSON.toJSONString(parameterValue));
                }
            }
        }
        try {
            Object responseObject = null;
            responseObject = proceedingJoinPoint.proceed();
            // 请求操作成功
            String responseDataJson = "";
            if (responseObject != null) {
                if (responseObject instanceof ResponseBase) {
                    // 按 ResponseBase<> 格式响应的情况
                    responseDataJson = JSON.toJSONString(responseObject);
                } else {
                    // 不按 ResponseBase<> 格式响应的情况
                    responseDataJson = String.valueOf(responseObject);
                }
            }
            // 统一日志前缀
            String logRequestBase = "ip: " + ipv4
                    + " method: " + method
                    + " uri: " + uri
                    + " class: " + clazz
                    + " func: " + func;
            String logRequestData = "request: " + requestDataBuilder.toString();
            String logResponseData = "response: " + responseDataJson;
            log.info("{} {} {}", logRequestBase, logRequestData, logResponseData);
            // 发布关键 Tag 到 Skywalking
            ActiveSpan.tag("ip", ipv4);
            ActiveSpan.tag("class", clazz);
            ActiveSpan.tag("func", func);
            long currentTime = System.currentTimeMillis();
            ActiveSpan.tag("cost", (currentTime - startTime) + "ms");
            ActiveSpan.info(logRequestData + " " + logResponseData);
            return responseObject;
        } catch (Throwable e) {
            // 请求过程异常
            String logRequestBase = "ip: " + ipv4
                    + " method: " + method
                    + " uri: " + uri
                    + " class: " + clazz
                    + " func: " + func;
            String logRequestData = "request: " + requestDataBuilder.toString();
            String logException = " exception: " + e.getMessage();
            log.warn("{} {} {}", logRequestBase, logRequestData, logException);
            // 发布关键 Tag 到 Skywalking
            ActiveSpan.tag("ip", ipv4);
            ActiveSpan.tag("class", clazz);
            ActiveSpan.tag("func", func);
            long currentTime = System.currentTimeMillis();
            ActiveSpan.tag("cost", (currentTime - startTime) + "ms");
            ActiveSpan.error(logRequestData + " " + logException);
            throw e;
        } finally {
            // 请求已完成，清除请求开始时间
            LogRunner.threadLocalTime.remove();
        }
    }

}